# -*- codeing = utf-8 -*-
# !/usr/bin/env python
# @coding  : utf-8
# @File    : t4.py
# @Time    : 2022/5/27 20:16
# @Author  : xdd
# @Software: PyCharm
# @desc :$
import random
import matplotlib.pyplot as plt
class Kmeans():
    def __init__(self, k):
        '''
        初始化
        :param k:代表聚类中心的个数
        '''
        self.__k = k
        self.__data = []  #存放原始数据,初次生成的100个点
        self.__pointCenter = []   #存放聚类中心点
        self.__result = []      #存放最后的聚类结果
        for i in range(k):      #默认有5个聚类，即生成[[],[],[],[],[]]
            self.__result.append([])   #五个子列表，存放分类的点
            pass
        pass

    def calDistance(self,points1,points2):
        '''
        欧式距离：sprt((x1-x2)^2+(y1-y2)^2)
        :param points1: 一维列表
        :param points2: 一维列表
        :return: 两个点之间的直线距离
        '''
        distance=(sum([(x1-x2)**2 for x1,x2 in zip(points1,points2)]))**0.5  #开平方等于乘以1/2次方
        return distance
        pass

    def randomCenter(self):
        '''
        生成self.__pointCenter：初次聚类中心点列表
        :return:
        '''
        while len(self.__pointCenter)<self.__k:
            index=random.randint(0,len(self.__data))  #得到0-len(self.__data)-1之间的索引
            if self.__data[index] not in self.__pointCenter:   #用索引值得到列表的值
                self.__pointCenter.append(self.__data[index])
                pass
        pass

    def calPointToCenterDistance(self,data,center):
        '''
        计算每个点和聚类中心点之间的距离
        :param data: 原始数据,初次生成的100个点
        :param center:  中心聚类点
        :return: 距离
        '''
        distance=[]
        for i in data:
            distance.append([self.calDistance(i,centerpoint) for centerpoint in center])
            pass
        return distance
        pass

    # def sortPoint(self,distance):
    #     '''
    #     对原始数据进行分类，将每个点分到离他最近的聚类中心点
    #     :param distance: 得到的距离值
    #     :return: 返回最终的分类结果
    #     '''
    #     for i in distance:
    #         index=i.index(min(i))  #得到五个距离值中的最小值的索引
    #         self.__result[index].append(self.__data[i])  #通过索引进行分类
    #         pass
    #     return self.__result
    #     pass

    def calNewCenterPoint(self,result):
        '''
        计算新的中心点：生成方式：通过生成的新的聚类求取新的平均值
        :param result: 分类结果
        :return: 返回新的聚类中心点
        '''
        newCenterPoint1=[]
        for temp in result:
            #进行转置，即将N*M转为M*N形式，即将所有point.x值和point.y值装置到一个列表中
            #例如：[[x1,y1],[x2,y2]] 装置后： [[x1,x2],[y1,y2]]。便于求取新的平均值
            temps=[[temp[x][i] for x in range(len(temp))] for i in range(len(temp[0]))]
            point=[]
            for i in temps:
                point.append(sum(i)/len(i))#求和在除以数组长度，求取平均值
                pass
            newCenterPoint1.append(point)
            pass
        return newCenterPoint1
        pass

    def calCenterToCenterDistance(self,old,new):
        '''
        迭代结束条件：
        计算新旧中心点之间的距离：
        :param old:
        :param new:
        :return:
        '''
        total=0
        for point1,point2 in zip(old,new):
            total += self.calDistance(point1,point2)
            pass
        return total/len(old)
        pass


    def fit(self,data,threshold,times=50000):
        self.__data = data
        self.randomCenter()
        print(self.__pointCenter)
        centerDistance = self.calPointToCenterDistance(self.__data, self.__pointCenter)

        # 对原始数据进行分类，将每个点分到离它最近的中心点
        i = 0
        for temp in centerDistance:
            index = temp.index(min(temp))
            self.__result[index].append(self.__data[i])
            i += 1
            pass
        # 打印分类结果
        # print(self.__result)
        oldCenterPoint = self.__pointCenter
        newCenterPoint = self.calNewCenterPoint(self.__result)

        while self.calCenterToCenterDistance(oldCenterPoint, newCenterPoint) > threshold:
            times -= 1
            result = []
            for i in range(self.__k):
                result.append([])
                pass
            # 保存上次的中心点
            oldCenterPoint = newCenterPoint
            centerDistance = self.calPointToCenterDistance(self.__data,newCenterPoint)

            # 对原始数据进行分类，将每个点分到离它最近的中心点
            i = 0
            for temp in centerDistance:
                index = temp.index(min(temp))
                result[index].append(self.__data[i])  # result = [[[10,20]]]
                i += 1
                pass

            newCenterPoint = self.calNewCenterPoint(result)
            print(self.calCenterToCenterDistance(oldCenterPoint, newCenterPoint))
            self.__result = result
            pass
        self.__pointCenter = newCenterPoint
        return newCenterPoint, self.__result
        pass

    pass


if __name__ == "__main__":
    data = [[random.randint(1, 100), random.randint(1, 100)] for i in range(1000)]
    for i in range(100):
        kmeans = Kmeans(k=5)
        centerPoint, result = kmeans.fit(data, 0.0001)
        print(centerPoint)
        plt.plot()
        plt.title("KMeans Classification")
        i = 0
        tempx = []
        tempy = []
        color = []
        for temp in result:
            temps = [[temp[x][i] for x in range(len(temp))] for i in range(len(temp[0]))]
            color += [i] * len(temps[0])
            tempx += temps[0]
            tempy += temps[1]

            i += 2
            pass
        plt.scatter(tempx, tempy, c=color, s=30)
        plt.show()
        pass
    pass
